﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Xml;
using System.Xml.XPath;

namespace Allegro.TaskExecution
{
    /// <summary>
    /// 
    /// </summary>
    public class AbortedTaskExecutionException : ApplicationException
    {
        internal AbortedTaskExecutionException(string msg)
            : base(msg)
        {

        }
    }
    /// <summary>
    /// 
    /// </summary>
    public class ContextVariable : PersistentItem, IContextVariable
    {
        //private string _name;
        private object _value;

        /// <summary>
        /// 
        /// </summary>
        public ContextVariable()
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <param name="value"></param>
        internal ContextVariable(string varName, object value)
        {
            Name = varName;
            _value = value;
        }
        
        /// <summary>
        /// 
        /// </summary>
        public object Value
        {
            get { return _value; }
            internal set { _value = value; }
        }

        // Overrides
        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            XmlPersist.SaveObject(writer, _value, persistenceContext);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            //itemNode.MoveToFirstChild();
            XPathNavigator valueNode = itemNode.CreateNavigator();
            if(valueNode.MoveToFirstChild())
                _value = XmlPersist.LoadObject(valueNode, persistenceContext);
            else
            {
                
            }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public class ProgressAggregator : IExecutionProgressAggregator
    {
        private Dictionary<object, double> _progressTable;
        private string _message = "";
        private object _item = null;

        ///<summary>
        ///</summary>
        public ProgressAggregator()
        {
            Reset();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Reset()
        {
            _progressTable = new Dictionary<object, double>();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="progress"></param>
        /// <param name="message"></param>
        public void UpdateProgress(object item, double progress, string message)
        {
            lock (_progressTable)
            {
                _message = message;
                _item = item;
                if (!_progressTable.ContainsKey(item))
                {
                    _progressTable.Add(item, progress);
                }
                else
                {
                    _progressTable[item] = progress;
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public double GetProgress(object item)
        {
            return (_progressTable.ContainsKey(item)) ? _progressTable[item] : 0.0;
        }
        /// <summary>
        /// 
        /// </summary>
        public string ProgressMessage
        {
            get { return _message; }
        }
        /// <summary>
        /// 
        /// </summary>
        public object LastUpdatedItem
        {
            get { return _item; }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public abstract class BasicTaskExecutionContext : PersistentItem, IExecutionContext, ITaskContextCoordination, ITaskExecutionControl
    {
        private Dictionary<string, IContextVariable> _variables;
        private int _modificationCount = 0;
        private int _varListCreatedAtCount = 0;
        private IList<IContextVariable> _variableList;
        Dictionary<ITask, ManualResetEvent> _completionEvents = null;
        private ManualResetEvent _suspension;
        private bool _aborted = false;
        private IExecutionProgressAggregator _progressAggregator = null;
        private object _this = null;
        private Dictionary<string, object> _breakpoints;

        ///<summary>
        ///</summary>
        public BasicTaskExecutionContext()
        {
            _variables = new Dictionary<string, IContextVariable>();
            _completionEvents = new Dictionary<ITask, ManualResetEvent>();
            _suspension = new ManualResetEvent(true);
            _breakpoints = new Dictionary<string, object>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        public object this[string varName]
        {
            get
            {
                return GetValue(varName);
            }
            set
            {
                SetValue(varName, value);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        public virtual object GetValue(string varName)
        {
            if (string.Equals(varName, "this"))
            {
                return This;
            }
            object value = null;
            Monitor.Enter(_variables);
            if (_variables.ContainsKey(varName))
            {
                value = _variables[varName].Value;
            }
            Monitor.Exit(_variables);
            return value;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <param name="value"></param>
        public virtual void SetValue(string varName, object value)
        {
            if (string.Equals(varName, "this"))
            {
                throw new ArgumentException("Context variable named 'this' can not be set");
            }
            Monitor.Enter(_variables);
            if (_variables.ContainsKey(varName))
                ((ContextVariable)_variables[varName]).Value = value;
            else
            {
                _variables.Add(varName, new ContextVariable(varName, value));
            }
            _modificationCount++;
            Monitor.Exit(_variables);
        }
        /// <summary>
        /// 
        /// </summary>
        public virtual IList<IContextVariable> Variables
        {
            get
            {
                IList<IContextVariable> vars = null;
                Monitor.Enter(_variables);
                if (_modificationCount > _varListCreatedAtCount)
                {
                    _variableList = vars = new ReadOnlyCollection<IContextVariable>(new List<IContextVariable>(_variables.Values));
                    _varListCreatedAtCount = _modificationCount;
                }
                else
                {
                    vars = _variableList;
                }
                Monitor.Exit(_variables);
                return vars;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        public virtual bool HasVariable(string varName)
        {
            if (string.Equals(varName, "this") && This != null)
                return true;
            Monitor.Enter(_variables);
            bool hasVar = _variables.ContainsKey(varName);               
            Monitor.Exit(_variables);
            return hasVar;
        }
        /// <summary>
        /// 
        /// </summary>
        public IExecutionProgressAggregator ProgressAggregator
        {
            get
            {
                //IExecutionProgressAggregator progressAggregator = _progressAggregator;
                if (_progressAggregator == null && this is ITaskContextDerivation)
                {
                    //Monitor.Enter(_variables);
                    // Recheck in order to avoid race condition
                    Monitor.Enter(_variables);
                    if (_progressAggregator == null)
                    {
                        IExecutionContext rootContext = ((ITaskContextDerivation)this).RootContext;
                        _progressAggregator = (rootContext != null)
                                                  ? rootContext.ProgressAggregator
                                                  : new ProgressAggregator();
                    }
                    Monitor.Exit(_variables);
                }
                return _progressAggregator;
            }
            set
            {
                _progressAggregator = value;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public object This
        {
            get { return _this; }
            protected set { _this = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        /// <param name="progress"></param>
        /// <param name="msg"></param>
        public void SetProgressAndCompute(ITask task, double progress, string msg)
        {
            ProgressAggregator.UpdateProgress(task, progress, msg);
            if (task is ITaskControl)
            {
                ((ITaskControl)task).ComputeProgress(this);
            }

        }
        /// <summary>
        /// 
        /// </summary>
        public virtual void CheckIfSuspendOrAbort()
        {
            ExitIfJobIsAborted();
            WaitIfJobIsSuspended();

        }

        /// <summary>
        /// 
        /// </summary>
        public abstract IExecutionContext OuterContext { get; }

        /// <summary>
        /// 
        /// </summary>
        public abstract IExecutionContext RootContext { get; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        public void RegisterTaskInContext(ITask task)
        {
            Monitor.Enter(_completionEvents);
            if (!_completionEvents.ContainsKey(task))
                _completionEvents.Add(task, new ManualResetEvent(false));
            else
            {
                _completionEvents[task].Reset();
            }
            Monitor.Exit(_completionEvents);
            This = task;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        public void WaitOnTaskCompletion(ITask task)
        {
            var completionEvent = CompletionEvent(task);
            if(completionEvent != null)
                completionEvent.WaitOne();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        public void Complete(ITask task)
        {

            // Update progress
            if (ProgressAggregator != null)
                ProgressAggregator.UpdateProgress(task, 1.0, string.Format("Task {0} completed", task.Name));

            // Trigger progress aggregation
            if (task is ITaskControl)
            {
                ((ITaskControl)task).ComputeProgress(this);
            }
            var completionEvent = CompletionEvent(task);
            if (completionEvent != null)
                completionEvent.Set();
            //if (_completionEvents.ContainsKey(task))
            //    _completionEvents[task].Set();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        /// <returns></returns>
        public EventWaitHandle CompletionEvent(ITask task)
        {
            // Must ensure thread safety
            Monitor.Enter(_completionEvents);
            EventWaitHandle completionEvent = (_completionEvents.ContainsKey(task)) ? _completionEvents[task] : null;
            Monitor.Exit(_completionEvents);
            return completionEvent;
        }

        /// <summary>
        /// 
        /// </summary>
        public void WaitIfJobIsSuspended()
        {
            IExecutionContext rootContext = null;
            if (this is ITaskContextDerivation)
            {
                rootContext = ((ITaskContextDerivation)this).RootContext;
            }
            if (rootContext != null && rootContext is ITaskContextCoordination)
            {
                ITaskContextCoordination control = rootContext as ITaskContextCoordination;
                control.WaitIfJobIsSuspended();
            }
            else
            {
                if (_suspension.WaitOne())
                {
                    //Console.WriteLine("Task execution job resumed");
                }
                else
                {
                    Console.WriteLine("Task continued");
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void ExitIfJobIsAborted()
        {
            IExecutionContext rootContext = null;
            if (this is ITaskContextDerivation)
            {
                rootContext = ((ITaskContextDerivation)this).RootContext;
            }
            if (rootContext != null && rootContext is ITaskContextCoordination)
            {
                ITaskContextCoordination control = rootContext as ITaskContextCoordination;
                control.ExitIfJobIsAborted();
            }
            else if (_aborted)
            {
                string msg = "Task was aborted";
                Console.WriteLine(msg);
                throw new AbortedTaskExecutionException(msg);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool IsAborted
        {
            get { return _aborted; }
            set { _aborted = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public void SuspendTaskExecution()
        {
            IExecutionContext rootContext = null;
            if (this is ITaskContextDerivation)
            {
                rootContext = ((ITaskContextDerivation)this).RootContext;
            }
            if (rootContext != null && rootContext is ITaskExecutionControl)
            {
                ITaskExecutionControl control = rootContext as ITaskExecutionControl;
                control.SuspendTaskExecution();
            }
            else
            {
                Console.WriteLine("**** Resetting suspension event at {0}", DateTime.Now);
                _suspension.Reset();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void ResumeTaskExecution()
        {
            IExecutionContext rootContext = null;
            if (this is ITaskContextDerivation)
            {
                rootContext = ((ITaskContextDerivation)this).RootContext;
            }
            if (rootContext != null && rootContext is ITaskExecutionControl)
            {
                ITaskExecutionControl control = rootContext as ITaskExecutionControl;
                control.ResumeTaskExecution();
            }
            else
            {
                _suspension.Set();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void AbortTaskExecution()
        {
            IExecutionContext rootContext = null;
            if (this is ITaskContextDerivation)
            {
                rootContext = ((ITaskContextDerivation)this).RootContext;
            }
            if (rootContext != null && rootContext is ITaskExecutionControl)
            {
                ITaskExecutionControl control = rootContext as ITaskExecutionControl;
                control.AbortTaskExecution();
            }
            else
            {
                _aborted = true;
            }
        }

        /// <summary>
        /// Add breakpoint
        /// </summary>
        /// <param name="taskId"></param>
        /// <param name="breakpointName"></param>
        public void AddBreakpoint(string taskId, string breakpointName)
        {
            if(RootContext == null)
            {
                string breakpointId = _GetFullBreakpointId(taskId, breakpointName);
                Monitor.Enter(_breakpoints);
                if (!_breakpoints.ContainsKey(breakpointId))
                    _breakpoints.Add(breakpointId, taskId);
                else
                {
                    Monitor.Exit(_breakpoints);
                    string msg = string.Format("Breakpoint {0} for task {1} already set", breakpointName, breakpointName);
                    Log.Error(msg, null, LogEventTypes.OtherEvent);
                    throw new ApplicationException(msg);
                }
                Monitor.Exit(_breakpoints);
            }
            else
            {
                if(RootContext is ITaskExecutionControl)
                {
                    ((ITaskExecutionControl) RootContext).AddBreakpoint(taskId, breakpointName);
                }
            }
        }
        
        /// <summary>
        /// Remove breakpoint
        /// </summary>
        /// <param name="taskId"></param>
        /// <param name="breakpointName"></param>
        public void RemoveBreakpoint(string taskId, string breakpointName)
        {
            string breakpointId = _GetFullBreakpointId(taskId, breakpointName);
            if (RootContext == null)
            {
                Monitor.Enter(_breakpoints);
                if (_breakpoints.ContainsKey(breakpointId))
                    _breakpoints.Remove(breakpointId);
                Monitor.Exit(_breakpoints);
            }
            else
            {
                if (RootContext is ITaskExecutionControl)
                {
                    ((ITaskExecutionControl) RootContext).RemoveBreakpoint(taskId, breakpointName);
                }
            }
        }

        /// <summary>
        /// List of breakpoints
        /// </summary>
        public IList<string> Breakpoints
        {
            get
            {
                IList<string> breakpoints = null;
                if(RootContext == null)
                {
                    Monitor.Enter(_breakpoints);
                    breakpoints = new List<string>(_breakpoints.Keys);
                    Monitor.Exit(_breakpoints);
                }
                else
                {
                    if(RootContext is ITaskExecutionControl)
                    {
                        breakpoints = ((ITaskExecutionControl) RootContext).Breakpoints;
                    }
                }
                return breakpoints;
            }
        }

        /// <summary>
        /// Return true if the breakpoint is set
        /// </summary>
        /// <param name="taskId"></param>
        /// <param name="breakPointName"></param>
        /// <returns></returns>
        public bool BreakpointIsSet(string taskId, string breakPointName)
        {
            bool isSet = false;
            string breakpointId = _GetFullBreakpointId(taskId, breakPointName);
            if (RootContext == null)
            {
                Monitor.Enter(_breakpoints);
                isSet = _breakpoints.ContainsKey(breakpointId);
                Monitor.Exit(_breakpoints);
            }
            else
            {
                if (RootContext is ITaskExecutionControl)
                {
                    isSet = ((ITaskExecutionControl) RootContext).BreakpointIsSet(taskId, breakPointName);
                }
            }
            return isSet;
        }

        // Protected/private methods

        /// <summary>
        /// 
        /// </summary>
        protected Dictionary<string, IContextVariable> ContextVariables
        {
            get { return _variables; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            if(_variables != null)
            {
                writer.WriteStartElement("Variables", Constants.XmlNamespace);
                foreach (IContextVariable var in _variables.Values)
                {
                    IXmlPersist varPersist = var as IXmlPersist;
                    varPersist.Save(writer, persistenceContext);
                }
                writer.WriteEndElement();
            }
            //if (Activity != null)
            //{
            //    writer.WriteStartElement("Activity", XmlNamespace);
            //    XmlPersist.SaveObject(writer, Activity, persistenceContext);
            //    writer.WriteEndElement();
            //}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            persistenceContext.NamespaceManager.AddNamespace(Constants.XmlDefaultNamespacePrefix, Constants.XmlNamespace);

            XPathNavigator varNode = itemNode.SelectSingleNode("al:Variables", persistenceContext.NamespaceManager);
            if(varNode != null)
            {
                if(varNode.MoveToFirstChild())
                {
                    _LoadVariable(varNode, persistenceContext);
                    while (varNode.MoveToNext())
                    {
                        _LoadVariable(varNode, persistenceContext);
                    }
                }
            }
            //XPathNavigator activityNode = itemNode.SelectSingleNode("al:Activity", persistenceContext.NamespaceManager);
            //if (activityNode != null)
            //{
            //    activityNode.MoveToFirstChild();
            //    if (activityNode != null)
            //        Activity = XmlPersist.LoadObject<ITaskActivity>(activityNode);
            //}
        }

        // private methods
        void _LoadVariable(XPathNavigator varNode, IXmlPersistenceContext persistenceContext)
        {
            IContextVariable var = XmlPersist.LoadObject<IContextVariable>(varNode,persistenceContext);
            _variables.Add(var.Name, var);
            _modificationCount++;
        }
        string _GetFullBreakpointId(string taskId, string breakpointName)
        {
            return taskId + "|" + breakpointName;
        }
        void _ParseBreakpointId(string breakpointId, out string taskId, out string breakpointName)
        {
            string[] parts = breakpointId.Split('|');
            if (parts.Length >= 2)
            {
                taskId = parts[0];
                breakpointName = parts[1];
            }
            else
            {
                string msg = string.Format("Illegal breakpoint id: {0}", breakpointId);
                Log.Error(msg, null, LogEventTypes.OtherEvent);
                throw new ApplicationException(msg);
            }
        }
    }
   
}
